package erpc

import (
	"context"
	"encoding/json"
	"fmt"
	"net"

	"github.com/tjfoc/gmsm/gmtls"
	"github.com/zeebo/errs"
)

type uploadStream struct {
	stream Stream
}

func (u *uploadStream) SendAndClose(result *UploadResult) error {
	data, err := json.Marshal(result)
	if err != nil {
		return err
	}
	buf := Int2bytes(len(data))
	buf2 := append(buf, data...)
	u.stream.Write(buf2)
	return nil
}

func (u *uploadStream) Recv() (*Chunk, error) {
	s := spliter{
		stream: u.stream,
	}

	size, err := s.readLen()
	if err != nil {
		return nil, errs.Combine(err, errs.New("readLen err"))
	}
	data, err := s.readN(size)
	if err != nil {
		return nil, errs.Combine(err, errs.New("readN err"))
	}
	if len(data) != size {
		return nil, errs.New("readN data != size")
	}
	var result = &Chunk{}
	err = json.Unmarshal(data, result)
	if err != nil {
		return nil, errs.Combine(err, errs.New("Unmarshal err"))
	}
	return result, nil
}
func (p *uploadStream) Close() {
	p.stream.Close()
}

////down
type downloadStream struct {
	stream Stream
}

func (p *downloadStream) Send(request *Chunk) error {
	data, err := json.Marshal(request)
	if err != nil {
		return err
	}

	buf := Int2bytes(len(data))
	buf2 := append(buf, data...)
	p.stream.Write(buf2)
	return nil
}
func (p *downloadStream) Close() {
	p.stream.Close()
}
func (p *downloadStream) CloseSend() error {
	_, err := p.stream.Write([]byte("close"))
	return err
}

type spliter struct {
	stream    Stream
	rpcServer RpcPieceStoreServer
}

func (s *spliter) readN(n int) ([]byte, error) {
	var buf = make([]byte, n)
	var m = 0
	for {
		nread, err := s.stream.Read(buf[m:])
		if err != nil {
			return nil, err
		}
		m += nread
		if m >= n {
			break
		}
	}
	return buf, nil
}

func (s *spliter) readLen() (int, error) {
	var buf = make([]byte, 4)
	var m = 0
	var n = 4
	for {
		nread, err := s.stream.Read(buf[m:])
		if err != nil {
			return 0, err
		}
		m += nread
		if m >= n {
			break
		}
	}
	return Bytes2Int(buf), nil
}

func (s *spliter) handle() error {
	for {
		size, err := s.readLen()
		if err != nil {
			return err
		}
		data, err := s.readN(size)
		if err != nil {
			return err
		}
		msg, err := Unmarshal(data, size)
		if err != nil {
			return err
		}
		fmt.Println(msg.MsgType)
		if msg.MsgType == MsgUpload {
			var request UploadRequest
			if err := json.Unmarshal(msg.Data, &request); err != nil {
				return err
			}
			_ = s.rpcServer.Upload(request, &uploadStream{
				stream: s.stream,
			})
		} else if msg.MsgType == MsgDownload {
			var request DownloadRequst
			if err := json.Unmarshal(msg.Data, &request); err != nil {
				return err
			}
			limit := s.stream
			if request.SpeedLimit > 0 {
				limit = NewLimitStream(s.stream, request.SpeedLimit)
			}
			_ = s.rpcServer.Download(request, &downloadStream{
				stream: limit,
			})

		} else {
			fmt.Println("unknow msg")
		}
	}
}

func handleConn(c net.Conn, rpcServer RpcPieceStoreServer) {
	defer func() {
		_ = c.Close()
		fmt.Println("close conn")
	}()
	s := spliter{
		stream:    c,
		rpcServer: rpcServer,
	}
	s.handle()
	//fmt.Println()
}

func StartRpcServer(ctx context.Context, addr string, s RpcPieceStoreServer, inscuremode bool) error {
	var l net.Listener
	if !inscuremode {
		//路径需修改
		cert, err := gmtls.LoadX509KeyPair("../../ca/server.crt", "../../ca/server.key")
		if err != nil {
			fmt.Println("LoadX509KeyPair error:", err)
			return err
		}

		config := &gmtls.Config{
			Certificates: []gmtls.Certificate{cert},
			ClientAuth:   gmtls.RequireAndVerifyClientCert,
		}
		l, err = gmtls.Listen("tcp", addr, config)
		if err != nil {
			fmt.Println("listen error:", err)
			return err
		}
	} else {
		var err error
		l, err = net.Listen("tcp", addr)
		if err != nil {
			fmt.Println("listen error:", err)
			return err
		}
	}

	go func() {
		for {
			select {
			case <-ctx.Done():
				fmt.Println("ctx.Done() , stop listen")
				l.Close()
				return
			}
		}
	}()
	for {
		c, err := l.Accept()
		if err != nil {
			fmt.Println("accept error:", err)
			break
		}
		// start a new goroutine to handle
		// the new connection.
		go handleConn(c, s)
	}

	return nil
}
